Skip to content

Cursor HITL: use Cursor's native approval (drop readOnlyHint + skip our elicitation)#40

Open
pragati-agrawal-glean wants to merge 2 commits into
mainfrom
fix/cursor-disable-readonlyhint-hitl
Open

Cursor HITL: use Cursor's native approval (drop readOnlyHint + skip our elicitation)#40
pragati-agrawal-glean wants to merge 2 commits into
mainfrom
fix/cursor-disable-readonlyhint-hitl

Conversation

@pragati-agrawal-glean

@pragati-agrawal-glean pragati-agrawal-glean commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Fix the Cursor HITL approval flow

Symptom: In Cursor, requires_approval actions via run_tool hang ~5 minutes with no approval prompt, then fail closed. Works fine in Claude Code.

Root cause (evidence-backed): a Cursor client regression (3.10.20 → 3.12.17), not a plugin/SDK change. Cursor 3.12.x only renders a server-initiated elicitation/create on its attended tool-call lane. run_tool marked readOnlyHint:true is classified auto-run/unattended, so the elicitation is silently dropped there. (readOnlyHint is git-constant since Jun 15; the elicitation payload is byte-identical regardless of the annotation; live logs show render on 3.10.20 vs a 300000ms timeout on 3.12.17.)

The fix — for Cursor, use Cursor's own native HITL instead of ours

Two paired changes, Cursor-only; Claude Code behavior is unchanged.

  1. Don't advertise readOnlyHint to Cursor (runToolAnnotations now takes the client identity). Without the hint, Cursor shows its native run-tool approval before executing run_tool.
  2. Skip our elicitation gate for Cursor (!isCursorClient added to the HITL gate in handleRunTool). With Cursor's native prompt already gating the call, our elicitation would be a redundant second gate — and it's the one that hangs. So Cursor executes directly after its native approval.

Net: Cursor → single native approval prompt; Claude Code → keeps readOnlyHint + our elicitation as before.

Validation

  • npm run typecheck clean; npx vitest run 189/189, incl. new cases: Cursor gets no readOnlyHint, and Cursor skips elicitation + executes directly.
  • Before/after demo (video): two local servers built from the same codebase differing only by this PR — main build hangs on a write action in Cursor; PR build shows Cursor's native approval and completes.

Notes / follow-ups

  • Temporary until the Cursor-side elicitation-rendering regression is fixed upstream (bug report to file; repro available).
  • buildApprovalMessage's Cursor-specific branch is now unreachable (elicitation never fires for Cursor) — harmless; can be cleaned up separately.
  • Latent hardening (not in this PR): the gate checks getClientCapabilities()?.elicitation truthy, but the SDK's elicitInput needs .elicitation.form.

🤖 Generated with Claude Code

Cursor 3.12.x only renders a server-initiated elicitation on its attended
tool-call lane. run_tool marked readOnlyHint:true lands on Cursor's
auto-run lane, where the HITL approval elicitation is silently dropped —
producing a 5-minute silent hang and a fail-closed timeout with no
approval banner. Cursor also strips tool `annotations` on ingest (and has
zero references to readOnlyHint in its bundle), so the hint never
suppressed a native prompt there anyway.

Thread the client identity into runToolAnnotations and omit readOnlyHint
when the client is Cursor (clientInfo.name starts with "cursor"), keeping
run_tool on the interactive lane so the banner renders. Claude Code is
unaffected: it renders elicitation regardless of lane, and the hint still
suppresses its native prompt.

Temporary until the Cursor-side rendering regression is fixed upstream.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@swarup-padhi-glean swarup-padhi-glean left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also have a demo video in the PR?

Pairs with omitting run_tool's readOnlyHint for Cursor. With the hint gone,
Cursor shows its own native run-tool approval before executing run_tool, so
our server-initiated elicitation is a redundant second gate — and Cursor
3.12.x silently drops it on the auto-run lane, hanging for the full HITL
timeout. Skip the elicitation gate for Cursor (add !isCursorClient to the
gate) and let its native prompt be the single approval; the tool then
executes directly. Claude Code is unchanged: keeps readOnlyHint and our
elicitation remains its gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pragati-agrawal-glean pragati-agrawal-glean changed the title Temp fix: don't advertise readOnlyHint to Cursor under HITL (unblocks approval banner) Cursor HITL: use Cursor's native approval (drop readOnlyHint + skip our elicitation) Jul 22, 2026
@pragati-agrawal-glean

Copy link
Copy Markdown
Contributor Author

@swarup-padhi-glean

Copy link
Copy Markdown
Contributor

Hey @pragati-agrawal-glean we will need to raise & investigate why we are having cursor's hitl for find_skills

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants